/*
 * Copyright (c) 2008-2012, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tansu_internal.h"
#include "tansu_status.h"
#include "tansu_nwif.h"
#include "tansu_socket.h"

struct tansu_nwif *
tansu_nwif_new()
{
    struct tansu_nwif *nwif;
    
    nwif = malloc(sizeof(struct tansu_nwif));
    if (nwif) {
        STAILQ_NEXT(nwif, entry) = NULL;
        nwif->name = NULL;
        nwif->family = AF_UNSPEC;
        nwif->addr = NULL;
        nwif->netmask = NULL;
        memset(nwif->hwaddr, 0, 6);
        nwif->flags = 0;
    }

    return nwif;
}

static uint16_t
tansu_nwif_getflags(uint16_t flags)
{
    uint16_t result = 0;
    if (flags & IFF_UP)
        result |= TANSU_NWIF_UP;
    if (flags & IFF_RUNNING)
        result |= TANSU_NWIF_RUNNING;
    if (flags & IFF_LOOPBACK)
        result |= TANSU_NWIF_LOOPBACK;
    return result;
}

/**
 * @retval 0 success
 * @retval 1 unsupported address family
 * @retval -1 memory allocation failed
 * @retval -2 inet_ntop() failed
 * @retval -3 strdup() failed
 * @retval -4 socket() failed
 */
static int
tansu_nwif_setaddrs(struct tansu_nwif *nwif, struct ifaddrs *ifap)
{
    char buff[INET6_ADDRSTRLEN];
    const void *src_addr;
    const void *src_netmask;
#ifndef __APPLE__
    int sock;
    struct ifreq ifreq;
#endif /* __APPLE__ */
    int af;

    af = ifap->ifa_addr->sa_family;
    if (af == AF_INET) {
        src_addr = &((struct sockaddr_in*)ifap->ifa_addr)->sin_addr;
        src_netmask = &((struct sockaddr_in*)ifap->ifa_netmask)->sin_addr;
    } else if (af == AF_INET6) {
        src_addr = &((struct sockaddr_in6*)ifap->ifa_addr)->sin6_addr;
        src_netmask = &((struct sockaddr_in6*)ifap->ifa_netmask)->sin6_addr;
    } else {
        return 1;
    }
    nwif->family = af;

    /* name */
    if ((nwif->name = strdup(ifap->ifa_name)) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "strdup() failed. errno %d\n", errno);
        return -3;
    }

    /* addr */
    if (inet_ntop(af, src_addr, buff, sizeof(buff)) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "inet_ntop() failed. errno %d\n", errno);
        return -2;
    }
    if ((nwif->addr = strdup(buff)) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "strdup() failed. errno %d\n", errno);
        return -3;
    }

    /* netmask */
    if (inet_ntop(af, src_netmask, buff, sizeof(buff)) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "inet_ntop() failed. errno %d\n", errno);
        return -2;
    }
    if ((nwif->netmask = strdup(buff)) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "strdup() failed. errno %d\n", errno);
        return -3;
    }

    /* hwaddr */
#ifdef __APPLE__
#ifndef TARGET_OS_IPHONE
    struct ifaddrs *ifa_list;
    struct ifaddrs *ifa;
    if (getifaddrs(&ifa_list) == 0) {
        for (ifa = ifa_list; ifa != NULL; ifa = ifa->ifa_next) {
            struct sockaddr_dl *dl = (struct sockaddr_dl*)ifa->ifa_addr;
            if (dl->sdl_family != AF_LINK || dl->sdl_type != IFT_ETHER)
                continue;
            size_t len = strlen(nwif->name);
            if (len != dl->sdl_nlen)
                continue;
            if (strncmp(nwif->name, dl->sdl_data, len) != 0)
                continue;
            if (sizeof(nwif->hwaddr) == dl->sdl_alen)
                memcpy(nwif->hwaddr, LLADDR(dl), 6);
        }
        freeifaddrs(ifa_list);
    }
#endif /* TARGET_OS_IPHONE */
#else /* __APPLE__ */
    sock = socket(af, SOCK_DGRAM, 0);
    if (sock == -1) {
        TLOG_ERROR(TLOG_NWIF, "socket() failed. errno %d\n", errno);
        return -4;
    }
    strncpy(ifreq.ifr_name, nwif->name, sizeof(ifreq.ifr_name));
    ifreq.ifr_name[sizeof(ifreq.ifr_name) - 1] = '\0';
    if (ioctl(sock, SIOCGIFHWADDR, &ifreq) == -1)
        TLOG_ERROR(TLOG_NWIF, "ioctl(SIOCGIFHWADDR) failed. errno %d\n", errno);
    else
        memcpy(nwif->hwaddr, ifreq.ifr_hwaddr.sa_data, 6);
    tansu_socket_close(sock);
#endif /* __APPLE__ */

    /* flags */
    nwif->flags = tansu_nwif_getflags(ifap->ifa_flags);

    return 0;
}

struct tansu_nwifs *
tansu_nwif_getaddrs(tansu_status_t *status)
{
    struct tansu_nwifs *head = NULL;
    struct ifaddrs *ifap;
    struct ifaddrs *tmp;
    struct tansu_nwif *nwif;
    tansu_status_t ret = TANSU_SUCCESS;

    if (getifaddrs(&ifap) != 0) {
        TLOG_ERROR(TLOG_NWIF, "getifaddrs() failed %d\n", errno);
        ret = TANSU_FAILED;
        goto out;
    }

    if ((head = malloc(sizeof(struct tansu_nwifs))) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "memory allocation failed\n");
        ret = TANSU_MEMORY_ERROR;
        goto out;
    }
    STAILQ_INIT(head);

    for (tmp = ifap; tmp != NULL; tmp = tmp->ifa_next) {
        if ((nwif = tansu_nwif_new()) == NULL) {
            TLOG_ERROR(TLOG_NWIF, "tansu_nwif_new() failed\n");
            ret = TANSU_MEMORY_ERROR;
            break;
        }
        if (tansu_nwif_setaddrs(nwif, tmp) != 0) {
            tansu_nwif_delete(nwif);
            nwif = NULL;
            continue;
        }
        STAILQ_INSERT_TAIL(head, nwif, entry);
    }

out:
    freeifaddrs(ifap);
    if (status)
        *status = ret;
    if (ret == TANSU_SUCCESS)
        return head;

    tansu_nwif_freeaddrs(head);
    return NULL;
}

void
tansu_nwif_delete(struct tansu_nwif *nwif)
{
    if (nwif) {
        STAILQ_NEXT(nwif, entry) = NULL;
 
        free(nwif->name);
        nwif->name = NULL;
        free(nwif->addr);
        nwif->addr = NULL;
        free(nwif->netmask);
        nwif->netmask = NULL;
 
        free(nwif);
        nwif = NULL;
    }
}

void
tansu_nwif_freeaddrs(struct tansu_nwifs *head)
{
    struct tansu_nwif *tmp;
    struct tansu_nwif *nwif;

    if (head == NULL)
        return;

    nwif = STAILQ_FIRST(head);
    while (nwif) {
        tmp = nwif;
        nwif = STAILQ_NEXT(nwif, entry);
        tansu_nwif_delete(tmp);
        tmp = NULL;
    }

    free(head);
    head = NULL;
}

